﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;

namespace ObdExpress.Ui.DataStructures
{
    /// <summary>
    /// Used to indicate what type of event occurred.
    /// </summary>
    public enum TopMenuEventType { MENU_MODIFIED, TOPMENU_SELECTED, MENUITEM_SELECTED };

    /// <summary>
    /// Delegate for handling the callback generated by a TopMenu being clicked.
    /// </summary>
    /// <param name="topMenu">A reference to the TopMenu that was clicked.</param>
    public delegate void TopMenuEvent(TopMenu topMenu, TopMenuEventType eventType);

    /// <summary>
    /// Represents one of the menus that comprises a NavigationMenu. Each TopMenu is parent to one or more MenuItems.
    /// </summary>
    public class TopMenu : INotifyPropertyChanged
    {
        private string _menuId;
        private string _menuLabel;
        private bool _isSelected;
        private List<MenuItem> _menuItems = new List<MenuItem>();
        private MenuItem _selectedItem = null;
        private event TopMenuEvent _topMenuEvent;

        /// <summary>
        /// Returns the unique ID representing this TopMenu.
        /// </summary>
        public string MenuId
        {
            get
            {
                return this._menuId;
            }
        }

        /// <summary>
        /// Returns the label set to be shown on this TopMenu's corresponding button.
        /// </summary>
        public string Label
        {
            get
            {
                return this._menuLabel;
            }
        }

        /// <summary>
        /// Gets or Sets if this TopMenu is selected. If so, this should be the only TopMenu in the NavigationMenu that is selected.
        /// </summary>
        public bool IsSelected
        {
            get
            {
                return this._isSelected;
            }
            set
            {
                this._isSelected = value;

                // Update any items monitoring this model
                this.NotifyPropertyChanged("IsSelected");
            }
        }

        /// <summary>
        /// Returns the MenuItem that is currently selected.
        /// </summary>
        public MenuItem SelectedItem
        {
            get
            {
                return this._selectedItem;
            }
        }

        /// <summary>
        /// Returns the Event Handler called by the UserControl representing this TopMenu.
        /// </summary>
        public MouseButtonEventHandler Click
        {
            get
            {
                return this.TopMenu_OnClick;
            }
        }

        /// <summary>
        /// Creates a new TopMenu.
        /// </summary>
        /// <param name="menuId">Unique ID representing this TopMenu.</param>
        /// <param name="menuLabel">The label to be shown on this TopMenu's corresponding button.</param>
        public TopMenu(string menuId, string menuLabel)
        {
            this._menuId = menuId;
            this._menuLabel = menuLabel;
        }

        /// <summary>
        /// Creates a new TopMenu.
        /// </summary>
        /// <param name="menuId">Unique ID representing this TopMenu.</param>
        /// <param name="menuLabel">The label to be shown on this TopMenu's corresponding button.</param>
        /// <param name="topMenuEventListener">A TopMenuEvent delegate to handle the Click action of this TopMenu's corresponding button.</param>
        public TopMenu(string menuId, string menuLabel, TopMenuEvent topMenuEventListener) : this(menuId, menuLabel)
        {
            if(topMenuEventListener != null)
            {
                this._topMenuEvent += topMenuEventListener;
            }
        }

        /// <summary>
        /// Add another MenuItem to this TopMenu
        /// </summary>
        /// <param name="menuItem">The new MenuItem to be added.</param>
        public void AddMenuItem(MenuItem menuItem)
        {
            // Get an Enumerator for the collection of MenuItems
            IEnumerator<MenuItem> menuItemsEnum = this._menuItems.GetEnumerator();

            // Determine if an item already exists with the new item's ID
            while (menuItemsEnum.MoveNext())
            {
                // If it does, throw an exception and quit
                if (menuItemsEnum.Current.Id.Equals(menuItem.Id))
                {
                    menuItemsEnum.Dispose();
                    throw new ApplicationException("Attempting to add MenuItem with non-unique ID.");
                }
            }

            // Attach this TopMenu's MenuItemEvent handler to the MenuItem's event so the TopMenu
            // may respond to it.
            menuItem.AddMenuItemEventListener(this.MenuItemEventListener);

            // If no matches are found, add the new item
            this._menuItems.Add(menuItem);

            // If this was the first item, set it as the selected item
            if (this._menuItems.Count == 1)
            {
                // Set the MenuItem as selected
                menuItem.IsSelected = true;

                // Store a reference to the selected item for this TopMenu
                this._selectedItem = menuItem;
            }

            // Trigger a TopMenuEvent
            if (this._topMenuEvent != null)
            {
                this._topMenuEvent(this, TopMenuEventType.MENU_MODIFIED);
            }
        }

        /// <summary>
        /// Remove a MenuItem by its ID.
        /// </summary>
        /// <param name="menuItemId">The ID of the MenuItem to be removed.</param>
        public void RemoveMenuItem(string menuItemId)
        {
            // Get an Enumerator for the collection of MenuItems
            IEnumerator<MenuItem> menuItemsEnum = this._menuItems.GetEnumerator();

            // Store a reference to the item to be removed
            MenuItem toRemove = null;

            // Determine if the iteme exists
            while (menuItemsEnum.MoveNext())
            {
                // If it does, remove it
                if (menuItemsEnum.Current.Id.Equals(menuItemId))
                {
                    toRemove = menuItemsEnum.Current;
                    menuItemsEnum.Dispose();
                    return;
                }
            }

            // If no matching item was found, throw an exception
            if (toRemove == null)
            {
                throw new ApplicationException("No MenuItem with a matching ID was found to be removed.");
            }
            else
            {
                // A match was found, so remove it
                this._menuItems.Remove(toRemove);

                // Trigger a TopMenuEvent
                if (this._topMenuEvent != null)
                {
                    this._topMenuEvent(this, TopMenuEventType.MENU_MODIFIED);
                }
            }
        }

        /// <summary>
        /// Registers another event listener to this TopMenu's TopMenuEvent.
        /// </summary>
        /// <param name="topMenuEventListener">The method listening to this TopMenu's TopMenuEvent.</param>
        public void AddTopMenuEventListener(TopMenuEvent topMenuEventListener)
        {
            if (topMenuEventListener != null)
            {
                this._topMenuEvent += topMenuEventListener;
            }
        }

        /// <summary>
        /// Removes an event listener from this TopMenu's TopMenuEvent.
        /// </summary>
        /// <param name="topMenuEventListener">The method to remove from this TopMenu's TopMenuEvent.</param>
        public void RemoveTopMenuEventListener(TopMenuEvent topMenuEventListener)
        {
            if (topMenuEventListener != null)
            {
                this._topMenuEvent -= topMenuEventListener;
            }
        }

        /// <summary>
        /// Returns an IEnumerator to enumerate the collection of MenuItems this TopMenu contains.
        /// </summary>
        /// <returns>An IEnumerator for the collection of MenuItems.</returns>
        public IEnumerator<MenuItem> GetEnumerator()
        {
            return this._menuItems.GetEnumerator();
        }

        /// <summary>
        /// Event handler registered on each MenuItem to allow the TopMenu to manage which items
        /// are selected and to bubble the event up to the NavigationMenu.
        /// </summary>
        /// <param name="menuItem">The MenuItem that evoked the event being handled.</param>
        public void MenuItemEventListener(MenuItem menuItem)
        {
            // De-select the old item
            this._selectedItem.IsSelected = false;

            // Set the new item as the selected item
            this._selectedItem = menuItem;

            // Set the newly selected item to selected
            this._selectedItem.IsSelected = true;

            // Bubble the event up
            this._topMenuEvent(this, TopMenuEventType.MENUITEM_SELECTED);
        }

        /// <summary>
        /// Event handler used to register to a specified event of the control used to select and display this TopMenu.
        /// </summary>
        /// <param name="sender">The object that evoked the action being handled.</param>
        /// <param name="e">Any arguments passed along with the action being handled.</param>
        public void TopMenu_OnClick(object sender, MouseEventArgs e)
        {
            // We only want to evoke an action for this TopMenu.
            // Whether it is selected, or not, should be handled at a higher level.
            if (!(this.IsSelected))
            {
                this._topMenuEvent(this, TopMenuEventType.TOPMENU_SELECTED);
            }
        }

        #region INotifyPropertyChanged

        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}
